<?php
	
	require_once('Joob/Utils/Helper.php');
	require_once('SQL.php');
	abstract class Joob_Db_Table {
		
		
		protected $verify;
		
		/**
		 * Class constructor
		 * @param string $db The name of the table in DB
		 */	
		function __construct($db=""){
			if ($db != "" && $db != null){
				$this->db = $db;
			}
			$this->error="";
            
			# Verification.
			$this->confirm(!Joob_Utils_Validator::isEmpty($this->db));
			
			$this->verify=new Joob_Utils_Validator($this);
		}
		
		
		/**
		 * LIST OF REGULAR FUNCTIONS
		 */
		
		
		# Function: setOption()
		# ---------------------------------------------
		# Setting local variable.
		
		public function setOption($opt, $value){
			$this->$opt=$value;
		}
		
		/**
		 * @return the value of the key. 
		 */
		public function key(){
			$key=$this->key;
			return $this->$key;
		}
        
		
		
		/**
		 * @return the last triggered error.
		 */
		public function getError(){
			return $this->error;
		}
		
		
		/**
		 * @desc Clear the error stored within the object.
		 */
		public function clearError(){
			$this->error="";
		}
		
		
		/**
		 * @desc Joob_Utils_Log message to files in system.
		 * @param string $message The message to be stored
		 * @param boolean $extra Extra parameter indicates whether
		 * we propagate the error in a common file.
		 */
		public function log($message,$extra=false){
//			Joob_Utils_Log::save($message,"dblogs/{$this->db}.txt");
//			if ($extra){
//				Joob_Utils_Log::save($message,"dblogs/all.txt");
//			}
		}
		
		
		/**
		 * @desc Reassign the database name.
		 * @param string $db
		 */
		public function db($db){
			$this->db=$db;
		}
		
		
		/**
		 * @desc Getter of the database (table) name.
		 */
		
		public function getDB(){
			return $this->db;
		}
		
        
        /**
        * @desc Check if the current object is connected.
        * @return True/false.
        */
        public function connected(){
            return $this->_connected;
        }
        
        
        /**
         * @desc Define a field in the of the class, just as the
         * way it is defined in the database.
         */
        final public function field(){
            
        }
        
		/**
		 * ACTION AND MESSAGE PASSING
		 */ 
		
        
        /**
         * @desc Connect to database, save information to local variable.
         * @category Read-access.
         */
		public function connect(){
			$key=$this->key();
			if (!Joob_Utils_Validator::isInt($key)){
				return false;
			}
			return $this->uconnect();
		}
		
		
			
		
		/**
		 * @desc Check if the table contain field = value. 
		 * @example 	 
		 * 		$user->contain('email','abc@abc.com') 
		 * @return: mixed. False if not containing the valid field; the corresponding row if the table contains.
		 * @param string $field
		 * @param int/string $value
		 * @category Read-access.
		 */
		public function contain($field,$value=null){
			if ($value===null) $value=$this->$field;
			$query="select * from {$this->db} where $field='$value' limit 1";
			$row=SQL::single($query);
			if ($row==null) return false;
			return $row;
		}
		
		
			
        /**
         * @desc Check if a specific column value is unique. Very 
         * convenient method for checking duplications in the database. 
         * @example 
         * 		When we want to check if the email is unique, use:
         * 		$user->unique('email'); 
         * 		Notice: The field $user->email has to be regularly constructed.
         * @param CSV $column
         * @access Read-access.
         */
        public function unique($column){
            $this->confirm(isset($this->$column));
            $value=$this->$column;
            $query="select * from {$this->db} where $column='$value' limit 1";
            $result=SQL::single($query);
            if ($result==null) return true;
            return false;
        }
        
        /**
         * @desc Check if the specific indexed fields existing in the
         * database.
         * @param String $fields
         * @return True/False.
         */
        public function exist($fields){
        	$fs=Joob_Utils_Word::split(",",$fields);
        	$q="";
        	
        	for ($i=0; $i<count($fs); $i++){
        		$field=$fs[$i];
        		$value=$this->$field;
        		$q.=" ($field='$value') ";
        		if ($i< count($fs)-1){
        			$q=$q." and ";
        		}
        	}
        	
        	
        	$r=$this->search($q,'*',1);
        	if (count($r)>=1){
        		return true;
        	}
        	return false;
        }
        
        
		
		/**
		 * @desc Insert fields into database. Fields are separated by commas
		 * @param String $fields
		 */
		public function insert($fields){
			$options=Joob_Utils_Word::split(",",$fields);
			if (count($options)<=0) return -1;
			
			$query="insert into {$this->db}";
			$f=""; $v="";
			for ($i=0; $i<count($options); $i++){
				$field=$options[$i];
				$v.="'".$this->$field."'";
				$f.=$field;
				if ($i<count($options)-1){
					$v=$v.",";$f=$f.",";
				}
			}			
			$query=$query."($f) values ($v)";
			$id=SQL::insert($query);
			if ($id>0){
				$key=$this->key;
				$this->$key=$id;
			}
			return $id;
		}
		
		
		/**
		 * @desc Fix a field, set value to $value
		 * @param String $field
		 * @param Mixed $value
		 */
		public function fix($field,$value=null){
			$identifier=$this->key;
			$key_value=$this->$identifier;
			if ($value===null) $value=$this->$field;
			$query="update {$this->db} set $field='$value' where ".$this->where()."";
			return SQL::update($query);
		}
	
		
		
		/**
		 * @desc Delete rows in database coressponding to this object.
		 */
		public function delete($where=""){
			if ($where=="")	{
				$where=$this->where();
			}
			$query="delete from {$this->db} where ".$where."";
			return SQL::update($query);
		}
		
		
		
		/**
		 * @desc Edit fields indicaded in the string $fields, changing to the 
		 * corresponding values. @example: $user->edit('name, email');
		 * @param $fields
		 * @return true/false
		 */
		public function edit($fields){
			$identifier=$this->key;
			$key_value=$this->$identifier;
			
			$options=Joob_Utils_Word::split(",",$fields);
			if (count($options)<=0) return false;

			$query="update {$this->db} set ";
			$subquery="";
			for ($i=0; $i<count($options); $i++){
				$field=$options[$i];
				$value=$this->$field;
				$subquery.=$field."='$value'";
				if ($i<count($options)-1){
					$subquery.=",";
				}
			}			
			$query=$query.$subquery." where ".$this->where()." limit 1";
			return SQL::write($query);
		}
		
		
		
		/**
		 * @desc Apply update with specify queries
		 * @param String $update
		 */
		public function applyUpdate($update){
			$query="update {$this->db} set $update where ".$this->where()."";
			return SQL::update($query);
		}
		
        
        
		/**
		 * @desc Search from database by $query. The return result 
		 * is an array. 
		 * 
		 * @example Search user with email containing '@yahoo' 
		 * 		$user->search("email like '%@yahoo'");
		 * 
		 * @param String $condition
		 * @param String $choice
		 * @param Int $limit
		 * @param String $order
		 */
        public function search($condition,$choice='*', $limit='50', $order='id DESC'){
            Joob_Utils_Word::clean($condition);
            if (Joob_Utils_Word::isEmpty($condition)){
                $query="select {$choice} from {$this->db} ";    
            }elseif (Joob_Utils_Word::initial($condition,5)=='where'){
                $query="select {$choice} from {$this->db} ".$condition;
            }else{
                $query="select {$choice} from {$this->db} where ".$condition;
            }
            
            $query.=" order by $order";
			if (!Joob_Utils_Word::isEmpty($limit)){
				$query.=" limit $limit";	
			}
            $results=SQL::toArray(SQL::get($query));
            $objs=array();
            for ($i=0; $i<count($results); $i++){
                $class=get_class($this);
                $obj=new $class;
                $obj->extend($results[$i]);
                $objs[]=$obj;
            }
            return $objs;
//			return $query;
        }
        
        
        /**
         * @desc Search with a condition. Return the single (first) object only.
         * @return Object, or null
         */
        public function searchSingle($condition){
        	$objs=$this->search($condition, '*', 1);
        	if (count($objs)==0){
        		return null;
        	}
        	return $objs[0];
        }
        
        
        
        /**
         * @desc Initializing the verification process
         * @param $type Output type on Error
         */
        public function verify($type=':ajax'){
        	$params=func_get_args();
        	if (count($params)<=1){
        		if (count($params)==0){
        			$type=':ajax';
        		}else{
        			$type=$params[0];
        		}
        		$this->verify->display($type);  
        		$this->verify->set($this);
        	}else{
        		$fn=$params[1];
        		$fields=$params[0];
        		$this->verify->$fn($fields);
        	}      
        }
        
        
        
        
        
		/**
         * @desc Static version of search. Only available if PHP version is
         * greater than 5.3. Install it if necessary. 
         * @param String $condition
         * @param String $choice
         * @param Int $limit 
         * @return An array of Object.
         */                                             
        public static function find($condition, $choice='*', $limit='50', $order_by=' id desc '){
        	Joob_Utils_Word::clean($condition);
            if (Joob_Utils_Word::isEmpty($condition)){
                $query="select {$choice} from ".static::$_db." ";    
            }elseif (Joob_Utils_Word::initial($condition,5)=='where'){
                $query="select {$choice} from ".static::$_db. "".$condition;
            }else{
                $query="select {$choice} from ".static::$_db." where ".$condition;
            }
            
            $query.=" order by $order_by";
            $results=SQL::toArray(SQL::read($query));
            $objs=array();
            for ($i=0; $i<count($results); $i++){
                $class=get_called_class();
                $obj=new $class;
                $obj->extend($results[$i]);
                $objs[]=$obj;
            }
            return $objs;
        }
        
		
        
        /**
         * @desc Find a single row, using condition boolen.
         * @return null, or object.
         */
        public static function findSingle($condition, $choice='*'){
        	$objs=static::find($condition,$choice, 1);
        	if (count($objs)>=1){
        		return $objs[0];
        	}
        	return null;
        }
        
        
		# TRANSACTIONAL FUNCTIONS
		
		# Function: commit()
		# --------------------------------------
		# Start a new transaction.
		
		public static function commit(){
			SQL::commit();
		}
		
		# Function: finish()
		# --------------------------------------
		# Finish the current transaction.
		
		public static function finish(){
			SQL::finish();
		}
		
		# Function: undone()
		# --------------------------------------
		# Roll back the current transaction.
		
		public static function undone(){
			SQL::undone();
		}        
        
		
		# Debug & alert function
		# ---------------------------------------------------
		# triggerError() and confirm()
		
		public function triggerError($error="NA"){
			error($error);
			$this->log("[Error] Message: $error","unconfirmed/{$this->db}.txt");
			$this->undone();
			$this->finish();
		}

		
		/**
		 * @desc Confirm that things've gone right, or die.
		 * @param Boolen $cond The condition to be evaluate
		 * @param String $error The message on error
		 */
		public function confirm($cond,$error="NA"){
			if (!$cond){
				$this->triggerError($error);
			}
		}
		
		
		/**
		 * @desc Connect to database using the unique id. Put information to local 
		 * variables. This is the connecting method for the primal case when 
		 * there is only one primary id (like User class).
		 */
		protected function uconnect(){
			$this->confirm(!Joob_Utils_Validator::isEmpty($this->key),'unable to connect.');
			$query="select * from {$this->db} where ".$this->where()." limit 1";
			$result=SQL::single($query);
			if ($result==null) return false;
			$this->extend($result);
            $this->_connected=true;
			return true;
		}
		
		public function extend($result)	{
			foreach($result as $key=>$value)	{
				$this->$key = $value;
			}
		}
		
        
        /**
         * @desc Construct the where condition.
         */
        protected function where(){
            $k=$this->key;
            confirm(isset($this->$k),'unset object identifier');
            return " {$this->key}='{$this->$k}' ";
        }
        
        
		# THE VARIABLE LISTS
		protected $db, $key;
		protected $error;
		
        public $_connected=false;
	}
	
?>